
import EventEmitter from 'events'
import PlayerEvents from './player-events'
import {ErrorTypes, ErrorDetails} from './player-errors'
import MSEEvents from '../core/mse-events'
import TransmuxingEvents from '../core/transmuxing-events'
import MSEController from '../core/mse-controller'
import Transmuxer from '../core/transmuxer'

class FlvPlayer {
    constructor(mediaDataSource) {
        this._emitter = new EventEmitter()
        this._config = createDefaultConfig()
        if (mediaDataSource.isLive === true) {
            this._config.isLive = true
        }

        this.e = {
            onvLoadedMetadata: this._onvLoadedMetadata.bind(this),
            onvSeeking: this._onvSeeking.bind(this),
            onvCanPlay: this._onvCanPlay.bind(this),
            onvStalled: this._onvStalled.bind(this),
            onvProgress: this._onvProgress.bind(this),
        }

        this._now = Date.now

        this._pendingSeekTime = null    // in seconds
        this._requestSetTime = false
        this._seekpointRecord = null
        this._progressChecker = null

        this._mediaDataSource = mediaDataSource
        this._mediaElement = null
        this._msectl = null
        this._transmuxer = null

        this._mseSourceOpened = false
        this._hasPendingLoad = false
        this._receivedCanPlay = false

        this._mediaInfo = null
        this._statisticsInfo = null

        this._alwaysSeekKeyframe = false
    }

    destroy() {
        if (this._progressChecker != null) {
            window.clearInterval(this._progressChecker)
            this._progressChecker = null
        }
        if (this._transmuxer) {
            this.unload()
        }
        if (this._mediaElement) {
            this.detachMediaElement()
        }
        this.e = null
        this._mediaDataSource = null
        this._emitter.removeAllListeners()
        this._emitter = null
    }

    on(event, listener) {
        if (event === PlayerEvents.MEDIA_INFO) {
            if (this._mediaInfo != null) {
                Promise.resolve().then(() => {
                    this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo)
                })
            }
        } else if (event === PlayerEvents.STATISTICS_INFO) {
            if (this._statisticsInfo != null) {
                Promise.resolve().then(() => {
                    this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo)
                })
            }
        }

        this._emitter.addListener(event, listener)
    }

    off(event, listener) {
        this._emitter.removeListener(event, listener);
    }

    attachMediaElement (mediaElement) {
        this._mediaElement = mediaElement

        // 监听media element 原生事件
        mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata)
        mediaElement.addEventListener('seeking', this.e.onvSeeking)
        mediaElement.addEventListener('canplay', this.e.onvCanPlay)
        mediaElement.addEventListener('stalled', this.e.onvStalled)
        mediaElement.addEventListener('progress', this.e.onvProgress)

        this._msectl = new MSEController(this._config)
        this._msectl.on(MSEEvents.UPDATE_END, this._onmseUpdateEnd.bind(this))
        this._msectl.on(MSEEvents.BUFFER_FULL, this._onmseBufferFull.bind(this))
        this._msectl.on(MSEEvents.SOURCE_OPEN, () => {
            this._mseSourceOpened = true
            if (this._hasPendingLoad) {
                this._hasPendingLoad = false
                this.load()
            }
        })
        this._msectl.on(MSEEvents.ERROR, (info) => {
            this._emitter.emit(
                PlayerEvents.ERROR,
                ErrorTypes.MEDIA_ERROR,
                ErrorDetails.MEDIA_MSE_ERROR,
                info
            )
        })
        this._msectl.attachMediaElement(mediaElement)
        if (this._pendingSeekTime != null) {
            try {
                mediaElement.currentTime = this._pendingSeekTime
                this._pendingSeekTime = null
            } catch (e) {
                // IE11 may throw InvalidStateError if readyState === 0
                // we can defer set currentTime operation after loadedmetadata
            }
        }
    }

    detachMediaElement() {}

    load() {
        if (!this._mediaElement) throw new Error('在load之前要attach')
        if (this._transmuxer) throw new Error('已经load过了，请先调用unload')
        if (this._hasPendingLoad) return
        
        if (
            this._config.deferLoadAfterSourceOpen &&
            this._mseSourceOpened === false
        ) {
            this._hasPendingLoad = true
            return
        }

        if (this._mediaElement.readyState > 0) {
            this._requestSetTime = true
            // IE11 may throw InvalidStateError if readyState === 0
            this._mediaElement.currentTime = 0
        }
        this._transmuxer = new Transmuxer(this._mediaDataSource, this._config)
        this._transmuxer.on(TransmuxingEvents.INIT_SEGMENT, (type, is) => {
            this._msectl.appendInitSegment(is)
        })
        this._transmuxer.on(TransmuxingEvents.MEDIA_SEGMENT, (type, ms) => {
            this._msectl.appendMediaSegment(ms)

            // lazyLoad check
            if (this._config.lazyLoad && !this._config.isLive) {
                let currentTime = this._mediaElement.currentTime
                if (
                    ms.info.endDts >=
                    (currentTime + this._config.lazyLoadMaxDuration) * 1000
                ) {
                    if (this._progressChecker == null) {
                        console.warning('FlvPlayer', 'Maximum beffering duration exceeded, suspend transmuxing task')
                        this._suspendTransmuxer()
                    }
                }
            }
        })
        this._transmuxer.on(TransmuxingEvents.LOADING_COMPLETE, () => {
            this._msectl.endOfStream()
            this._emitter.emit(PlayerEvents.LOADING_COMPLETE)
        })
        this._transmuxer.on(TransmuxingEvents.RECOVERED_EARLY_EOF, () => {
            this._emitter.emit(PlayerEvents.RECOVERED_EARLY_EOF)
        })
        this._transmuxer.on(TransmuxingEvents.IO_ERROR, (detail, info) => {
            this._emitter.emit(PlayerEvents.ERROR, ErrorTypes.NETWORK_ERROR, detail, info)
        })
        this._transmuxer.on(TransmuxingEvents.DEMUX_ERROR, (detail, info) => {
            this._emitter.emit(PlayerEvents.Error, ErrorTypes.MEDIA_ERROR, detail, {code: -1, msg: info})
        })
        this._transmuxer.on(TransmuxingEvents.MEDIA_INFO, (mediaInfo) => {
            this._mediaInfo = mediaInfo
            this._emitter.emit(PlayerEvents.MEDIA_INFO, Object.assign({}, mediaInfo))
        })
        this._transmuxer.on(TransmuxingEvents.METADATA_ARRIVED, (metadata) => {
            this._emitter.emit(PlayerEvents.METADATA_ARRIVED, metadata)
        })
        this._transmuxer.on(TransmuxingEvents.SCRIPTDATA_ARRIVED, (data) => {
            this._emitter.emit(PlayerEvents.SCRIPTDATA_ARRIVED, data)
        })
        this._transmuxer.on(TransmuxingEvents.STATISTICS_INFO, (statInfo) => {
            this._statisticsInfo = this._fillStatisticsInfo(statInfo)
            this._emitter.emit(PlayerEvents.STATISTICS_INFO, Object.assign({}, this._statisticsInfo))
        })
        this._transmuxer.on(TransmuxingEvents.RECOMMEND_SEEKPOINT, (milliseconds) => {
            if (this._mediaElement && !this._config.accurateSeek) {
                this._requestSetTime = true
                this._mediaElement.currentTime = milliseconds / 1000
            }
        })
        this._transmuxer.open()

    }

    _onvLoadedMetadata(e) {
        if (this._pendingSeekTime != null) {
            this._mediaElement.currentTime = this._pendingSeekTime
            this._pendingSeekTime = null
        }
    }

    _onvSeeking(e) { // handle seeking request from browser's progress bar
        let target = this._mediaElement.currentTime
        let buffered = thsi._mediaElement.buffered

        if (this._requestSetTime) { // ???
            this._requestSetTime = false
            return
        }

        if (target < 1.0 && buffered.length > 0) {
            // seek to video begin, set currentTime directly if beginPTS buffered
            let videoBeginTime = buffered.start(0)
            if (
                (videoBeginTime < 1.0 && target < videoBeginTime) ||
                false // isSafari
            ) {
                this._requestSetTime = true
                this._mediaElement.currentTime = videoBeginTime
            }
        }

        this._seekpointRecord = {
            seekPoint: target,
            recordTime: this._now()
        }
        window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50)
    }

    _onvCanPlay(e) {
        this._receivedCanPlay = true
        this._mediaElement.removeEventListener('canplay', this.e.onvCanPlay)
    }

    _onvStalled(e) {
        this._checkAndResumeStuckPlayback(true)
    }

    _onvProgress(e) {
        this._checkAndResumeStuckPlayback()
    }

    _checkAndResumeStuckPlayback(stalled) {
        let media = this._mediaElement
        if (
            stalled || !this._receivedCanPlay ||
            media.readyState < 2
        ) {
            // HAVE_CURRENT_DATA
            let buffered = media.buffered
            if (
                buffered.length > 0 &&
                media.currentTime < buffered.start(0)
            ) {
                console.warning(
                    `FlvPlayer`,
                    `Playback seems stuck at ${media.currentTime}, seek to ${buffered.start(0)}`
                )
                this._requestSetTime = true
                this._mediaElement.currentTime = buffered.start(0)
                this._mediaElement.removeEventListener('progress', this.e.onvProgress)
            }
        } else {
            // Playback didn't stuck, remove progress event listener
            this._mediaElement.removeEventListener('progress', this.e.onvProgress)
        }
    }

    _checkAndApplyUnbufferedSeekpoint() {
        if (this._seekpointRecord) {
            if (this._seekpointRecord.recordTime <= this._now() - 100) {
                let target = this._mediaElement.currentTime
                this._seekpointRecord = null
                if (!this._isTimepointBuffered(target)) {
                    if (this._progressChecker != null) {
                        window.clearTimeout(this._progressChecker)
                        this._progressChecker = null
                    }

                    // 真正seek
                    this._msectl.seek(target)
                    this._transmuxer.seek(Math.floor(target * 1000))

                    if (this._config.accurateSeek) {
                        this._requestSetTime = true
                        this._mediaElement.currentTime = target
                    }
                }
            } else {
                window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50)
            }
        }
    }

    _isTimepointBuffered(seconds) {
        let buffered = this._mediaElement.buffered

        for (let i = 0; i < buffered.length; i++) {
            let from = buffered.start(i)
            let to = buffered.end(i)
            if (seconds >= from && seconds < to) {
                return true
            }
        }
        return false
    }

    _fillStatisticsInfo(statInfo) {
        throw new Error('_fillStatisticsInfo 需要被实现')
    }

    _suspendTransmuxer() {
        if (this._transmuxer) {
            this._transmuxer.pause()

            if (this._progressChecker == null) {
                this._progressChecker = window.setInterval(
                    this._checkProgressAndResume.bind(this),
                    1000
                )
            }
        }
    }

    _checkProgressAndResume() {
        throw new Error('待实现')
        // let currentTime = this.
    }

    play() {
        return this._mediaElement.play()
    }

    pause() {
        return this._mediaElement.pause()
    }
}

export default FlvPlayer